/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */
#include <stdlib.h>
#include <stdio.h>
#include "uart.h"
#include <string.h>
#include "AT32_Bootloader_UART_Protocol.h"

struct at32_bl_cmds at32_bl_cmds[19] = {
    {
        AT32_CMD_Get_Commands,
        "--getCommands",
        "Get device command list",
    },
    {
        AT32_CMD_Get_Version,
        "--getVersion",
        "Get bootloader version",
    },
    {
        AT32_CMD_Get_Device_ID,
        "--getDeviceID",
        "Get device ID",
    },
    {
        AT32_CMD_Read_memory,
        "--readMemory",
        "Read memory at a given address",
    },
    {
        AT32_CMD_Jump,
        "--jump",
        "Jump to a given memory address",
    },
    {
        AT32_CMD_Write_memory,
        "--writeMemory",
        "Write to memory at a given address",
    },
    {
        AT32_CMD_Erase,
        "--erase",
        "Erase a given memory area or the entire chip or blank",
    },
    {
        AT32_CMD_Erase_and_program_protect,
        "--eraseAndProgramProtect",
        "Erase/program protection enable",
    },
    {
        AT32_CMD_Erase_and_program_unprotect,
        "--eraseAndProgramUnprotect",
        "Erase/program protection disable",
    },
    {
        AT32_CMD_Access_Protect,
        "--accessProtect",
        "Access protection enable",
    },
    {
        AT32_CMD_Access_Unprotect,
        "--accessUnprotect",
        "Access protection disable",
    },
    {
        AT32_CMD_Firmware_CRC,
        "--firmwareCRC",
        "Calculate sector CRC",
    },
    {
        AT32_CMD_Enable_SPIM,
        "--enableSPIM",
        "SPIM enable",
    },
    {
        AT32_CMD_Enable_sLib,
        "--enablesLib",
        "SLib enable",
    },
    {
        AT32_CMD_Disable_sLib,
        "--disablesLib",
        "SLib disable",
    },
    {
        AT32_CMD_Get_sLib_Status,
        "--getsLibStatus",
        "Get SLib status",
    },
    {
        AT32_CMD_SPIM_Remap,
        "--SPIMRemap",
        "SPIM IO PIN remap",
    },
    {
        AT32_CMD_Reset_Device,
        "--resetDevice",
        "Reset device",
    },
    {AT32_CMD_Advanced_Access_Protect, "--advancedAccessProtect", "Advanced access protection enable"},
};
int is_at32_cmd(uint8_t cmdValue)
{
    for (int i = 0; i < 19; i++)
    {
        if (cmdValue == at32_bl_cmds[i].value)
        {
            return i;
        }
    }
    return -1;
}
int at32_show_match_cmds(uint8_t *cmdValues, int len)
{
    for (int i = 0; i < len; i++)
    {
        int cmdIndex = is_at32_cmd(cmdValues[i]);
        if (cmdIndex != -1)
        {
            printf(" %-32s %s\n", at32_bl_cmds[cmdIndex].name, at32_bl_cmds[cmdIndex].usage);
        }
    }
    return 0;
}
static int at32_serial_write(at32_bup_t *b, uint8_t *data, uint16_t len)
{
}
static uint8_t at32_checksum(uint8_t *data, uint16_t len)
{
    uint8_t sum = 0;
    for (uint16_t i = 0; i < len; i++)
    {
        sum ^= data[i];
    }
    return sum;
}
uint8_t at32_wait_ack(at32_bup_t *b)
{
    uint8_t ack = 0;
    uint32_t wait = 0;
    while (1)
    {
        if (b->read(&ack, 1) == 1)
        {
            return ack;
        }
        if (wait++ >= 5)
        {
            return 0;
        }
    }
    return 0;
}
static int at32_send_cmd(at32_bup_t *b, uint8_t cmd)
{
    if (!b->write || !b->read || !b->delay_ms)
        return 1;

    uint8_t ret = 0;
    uint8_t try = 0;
    while (try++ < 1)
    {
        uint8_t buf[2] = {cmd, ~cmd};
        b->clearIo();
        ret = b->write(buf, 2);
        ret = at32_wait_ack(b);
        if (ret != 0)
            break;
    }
    return ret;
}
static int at32_send_data(at32_bup_t *b, uint8_t *data, uint16_t len)
{
    if (!b->write || !b->read || !b->write || !b->delay_ms)
        return 1;
    uint8_t checkSum = at32_checksum(data, len);
    b->clearIo();
    b->write(data, len);
    b->write(&checkSum, 1);
    return at32_wait_ack(b);
}
int at32_sync(at32_bup_t *b)
{
    printf("\nStart Sync ...\n");
    int try = 0;
    while (try++ < 3)
    {
        // b->hardwareReset();
        b->restToIspMode();
        uint8_t cmd = AT32_CMD_SYNC;
        b->clearIo();
        int ret = b->write(&cmd, 1);
        uint8_t ack = at32_wait_ack(b);
        if (ack == AT32_CMD_ACK || cmd == AT32_CMD_NACK)
        {
            b->synced = 1;
            return 0;
        }
        // if(try)
        // if (ret != 1)
        // {
        //     printf("write error %d\n", ret);
        // }
        // // b->delay_ms(10);
        // ret = b->read(&cmd, 1);
        // if (ret == 1)
        // {
        //     if (cmd == AT32_CMD_ACK || cmd == AT32_CMD_NACK)
        //     {
        //         b->synced = 1;
        //         printf("Sync OK!\n");
        //         return 0;
        //     }
        // }
        // else
        // {
        //     // printf("read error %d\n",ret);
        // }

        // b->delay_ms(100);
    }
    printf("Sync Failed!\n");
    return -1;
}
/**
 * @brief Set ISP 命令作用是为了区别不同上位机，在支持此命令的微控制器系列中，需要发送此命令，对应
的 Get Commands 命令和 Get Device ID 命令才能响应正确的数据。对不支持此命令的微控制器系
列，是否发送此命令，对后续的命令都没有影响。为了不同微控制流程兼容，主机端对此命令可分成
两种情况处理：
 主机发送 Set ISP 命令，设备在响应主机端 ACK，主机端继续发送 4 字节的上位机识别码，固
定为“0x02, 0x03, 0x54, 0x41”, 接着再发送 1 字节的校验和，设备再次响应主机 ACK，表示此命
令结束，可以进行下条命令的发送。
 主机端发送 Set ISP 命令，设备响应主机端 NACK，主机端收到此 NACK 响应之后，表示命令结
束，直接结束当前命令的后续流程，进入下一条命令的发送。
此命令在访问保护开启时也能使用。
 *
 * @param b
 * @return int
 */
int at32_set_isp(at32_bup_t *b)
{
    printf("\nStart Set ISP ...\n");
    int ret = 0;
    // if(b->hardwareReset) b->hardwareReset();
    if (!b->synced)
    {
        at32_sync(b);
    }
    uint8_t ack = at32_send_cmd(b, AT32_CMD_Set_ISP);
    if (ack == AT32_CMD_ACK)
    {
        printf("Set ISP CMD OK!\n");
        printf("send identification code ...\n");
        uint8_t identificationCode[4] = {0x02, 0x03, 0x54, 0x41};
        ack = at32_send_data(b, identificationCode, 4);
        // b->delay_ms(10);
        if (ack == AT32_CMD_ACK)
        {
            printf("Set ISP ID OK!\n");
        }
        else
        {
            printf("Set ISP data Failed! %02X\n\n", ret);
            ret = 1;
        }
    }
    else
    {
        // ret = 2;
        printf("Set ISP cmd Failed! %d\n", ret);
        printf("Target not support SET ISP CMD!\n");
    }
    return ret;
}
/**
 * @brief Get Commands 命令用于获取协议版本号和设备支持的命令列表，对应不同系列的微控制器，支持
的命令个数不同，返回的列表就会有所不同。
Bootloader 在收到此命令之后，会响应主机 ACK，接着会响应主机 1 字节的数据来表明接下来要传
送的数据长度，再返回协议版本和支持命令列表，然后再响应 ACK。
此命令在访问保护开启时也能使用。
注意：AT32F413xx，AT32F415xx，AT32F403Axx，AT32F407xx，AT32F421xx，AT32A403Axx，
这几个系列在发送 Get Commands 之前需要发送 Set ISP 命令
 *
 * @param b
 * @return int
 */
int at32_get_commands(at32_bup_t *b)
{
    // printf("\nStart Get Commands ...\n");
    int ret = 0;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ret = at32_send_cmd(b, AT32_CMD_Get_Commands);
    if (ret == AT32_CMD_ACK)
    {
        // printf("Get Commands CMD OK!\n");
        ret = 0;
    }
    else
    {
        printf("Get Commands cmd Failed! %d\n", ret);
        ret |= 0x1;
    }
    // b->delay_ms(10);
    uint8_t supportedCommands[64];
    uint8_t supportedCmdCnt, bootloaderVersion, ack;
    b->read(&supportedCmdCnt, 1);
    b->read(&bootloaderVersion, 1);
    b->read(supportedCommands, supportedCmdCnt);
    printf("Supported Commands(V%d): \n", bootloaderVersion);
    for (int i = 0; i < supportedCmdCnt; i++)
    {
        printf("%02X ", supportedCommands[i]);
    }
    printf("\n");

    b->read(&ack, 1);
    if (ack == AT32_CMD_ACK)
    {
        b->targetVersion = bootloaderVersion;
        b->targetCmdCount = supportedCmdCnt;
        memset(b->targetCmds, 0, sizeof(b->targetCmds));
        memcpy(b->targetCmds, supportedCommands, supportedCmdCnt);
        printf("Get Commands OK! Target supported commands(V%d):\n", bootloaderVersion);
        at32_show_match_cmds(supportedCommands, supportedCmdCnt);
    }
    else
    {
        printf("Get Commands Failed! %02X\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Get Version 命令用来读取设备端 Bootloader 的版本号，当设备端收到此命令后，会响应主机 ACK，
接着会传送 1 字节的 Bootloader 协议版本号和 2 个字节的 Bootloader 版本号（简称 BID），然后再
响应主机 ACK，表示命令结束。
此命令在访问保护开启时也能使用。
 *
 * @param b
 * @return int
 */
int at32_get_version(at32_bup_t *b)
{
    // printf("\nStart Get Version ...\n");
    int ret = 0;
    if (!b->synced)
    {
        at32_sync(b);
    }
    uint8_t ack = at32_send_cmd(b, AT32_CMD_Get_Version);
    if (ack == AT32_CMD_ACK)
    {
        // printf("Get Version CMD OK!\n");
    }
    else
    {
        printf("Get Version cmd Failed! %d\n", ret);
        ret |= 0x1;
    }
    // b->delay_ms(10);
    uint8_t protocolVersion, BID[2];
    b->read(&protocolVersion, 1);
    b->read(BID, 2);
    b->read(&ack, 1);
    if (ack == AT32_CMD_ACK)
    {
        b->targetProtocolVersion = protocolVersion;
        b->BID = (BID[1] << 8) | BID[0];
        printf("Get Version OK! Target protocol version: %d ", protocolVersion);
        printf("Target BID: %02X %02X\n", BID[1], BID[0]);
    }
    else
    {
        printf("Get Version Failed! %02X\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Get Device ID 命令用于读取微控制器具体型号，响应的数据中包含 4 字节 Product ID 号和 1 字节的
Project ID。主机端可以根据 1 字节的 Project ID 判断当前连续的设备是哪个系列的微控制器，通过 4
字节的 Product ID 号判断具体的某一个型号。
当设备端收到此命令后，会先响应主机 ACK，接着会传送 1 字节数据长度，表示要传送的数据长
度，其值为具体长度减 1（如后续传送数据长度为 5，则此值为 4），接着再传送 4 字节的 Product ID
和 1 字节的 Project ID，然后再响应一个 ACK。
此命令在访问保护开启时也能使用。
 *
 * @param b
 * @return int
 */
int at32_get_device_id(at32_bup_t *b)
{
    printf("\nStart Get Device ID ...\n");
    int ret = 0;
    if (!b->synced)
    {
        at32_sync(b);
    }
    uint8_t ack = at32_send_cmd(b, AT32_CMD_Get_Device_ID);
    if (ack == AT32_CMD_ACK)
    {
        printf("Get Device ID CMD OK!\n");
    }
    else
    {
        printf("Get Device ID cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    // b->delay_ms(10);
    uint8_t deviceID[5], idLen;
    b->read(&idLen, 1);
    b->read(deviceID, idLen + 1);
    b->read(&ack, 1);
    if (ack == AT32_CMD_ACK)
    {
        b->deviceID = (uint64_t)deviceID[4] << 32 | (uint64_t)deviceID[2] << 24 | (uint64_t)deviceID[3] << 16 | (uint64_t)deviceID[0] << 8 | deviceID[1];
        extern char *at_get_mcu_info(uint64_t deviceID);
        char *mcuInfo = at_get_mcu_info(b->deviceID);
        printf("Get Device ID OK!");
        printf("Target Device ID: [%016LX]\n", b->deviceID);
        if (mcuInfo != NULL)
        {
            printf("%s\n\n", mcuInfo);
        }
    }
    else
    {
        printf("Get Device ID Failed! %02X\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Read Memory 命令用来读取存储器，SRAM，启动程序代码区，用户系统数据区等有效地址范围内
 * 的数据，各种型号可允许读取的范围不同。在支持 SPIM 的型号上，要读取 SPIM 的数据，必须先使
能 SPIM(发送 Enable SPIM 命令)。
当设备收到此命令后，如果访问保护没有开启，会响应主机 ACK，接着会等待 4 字节的地址和 1 字
节的地址 Checksum，当 Checksum 正确且地址有效时，响应主机 ACK，再等待 1 字节的读取长度
及其 Checksum，长度的值为实际要读取的值长度减 1(如读 10 字节数据，此值为 9)，当 Checksum
正确，响应主机 ACK 之后，将开始传送对应地址的数据。
此命令在访问保护开启时不能使用。
 * @param b
 * @param addr
 * @param buf
 * @param len
 * @return int
 */
int at32_read_memory(at32_bup_t *b, uint32_t addr, uint8_t *buf, uint8_t len)
{
    // printf("\nStart Read Memory ...\n");
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Read_memory);
    if (ack == AT32_CMD_ACK)
    {
        // printf("Read Memory CMD OK!\n");
    }
    else
    {
        printf("Read Memory cmd Failed! %d\n", ret);
        return ret |= 0x1;
    }
    // b->delay_ms(1);
    uint8_t addrBuf[5];
    addrBuf[0] = (addr >> 24) & 0xFF;
    addrBuf[1] = (addr >> 16) & 0xFF;
    addrBuf[2] = (addr >> 8) & 0xFF;
    addrBuf[3] = addr & 0xFF;
    addrBuf[4] = 0;
    for(int i = 0; i < 4; i++)
    {
        addrBuf[4] ^= addrBuf[i];
    }
    b->write(addrBuf, 5);
    ack = at32_wait_ack(b);
    // ack = at32_send_data(b, addrBuf, 4);
    if (ack == AT32_CMD_ACK)
    {
        // printf("send addr OK!\n");
    }
    else
    {
        printf("send addr Failed! %d\n");
       return ret |= 0x2;
    }
    uint8_t lenBuf[2] = {len - 1, (len - 1) ^ 0xFF};
    b->write(lenBuf, 2);
    // b->delay_ms(5);
    ack = at32_wait_ack(b);
    if (ack == AT32_CMD_ACK)
    {
        uint16_t readlen = b->read(buf, len);
        if (readlen < 256)
        {
            printf("Read Memory OK! Read 0x%08X len: %d\n",addr, readlen);
            for (int i = 0; i < readlen; i++)
            {
                if (i % 32 == 0 && i != 0)
                    printf("\n");
                printf("%02X ", buf[i]);
            }
            printf("\n");
        }
    }
    else
    {
        printf("Read Memory Failed! at 0x%08X %d\n", addr, len);
        ret |= 0x10;
    }
    return ret;
}
/**
 * @brief Jump 命令用于跳转到指定地址进行执行，可以跳转到主存储器和 SRAM 中去执行，跳转的地址必须
是有效范围内的地址，需要注意各个型号的有效地址范围不同。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待 4 字节的跳转地址及
其 Checksum，当收到的地址和 Checksum 都有效时，响应主机 ACK 后，将跳转到响应地址进行执
行程序。
此命令在访问保护开启时不能使用。
注意：对于 AT32F421xx 系列，Jump 命令可能会跳转失败。
 *
 * @param b
 * @param addr
 * @return int
 */
int at32_jump_to_address(at32_bup_t *b, uint32_t addr)
{
    printf("\nStart Jump ...\n");
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Jump);
    if (ack == AT32_CMD_ACK)
    {
        printf("Jump CMD OK!\n");
    }
    else
    {
        printf("Jump cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    // b->delay_ms(1);
    uint8_t addrBuf[4];
    addrBuf[0] = (addr >> 24) & 0xFF;
    addrBuf[1] = (addr >> 16) & 0xFF;
    addrBuf[2] = (addr >> 8) & 0xFF;
    addrBuf[3] = addr & 0xFF;
    ack = at32_send_data(b, addrBuf, 4);
    if (ack == AT32_CMD_ACK)
    {
        printf("send addr OK!\n");
    }
    else
    {
        printf("send addr Failed! %d\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Write Memory 命令用于将数据写入主存储器，SRAM，用户系统数据区等，在写入主存储器之前，
需要先擦除对应地址的数据，另外写入的地址必须是有效范围内的地址，需要注意各个型号的有效地
址范围不同。
用户系统数据区由于不同型号范围不同，会有不同处理：
 用户系统数据区小于 256 字节
主机只发一次写命令将所有用户系统数据写入，设备端在收到命令之后会自动擦除用户系统数据
区，然后将数据写入，最后自动执行系统复位。
 用户系统系统数据区大于 256 字节
主机需要发送多次写命令，才能将用户系统数据写入
设备端在收到命令之后，如果地址为用户系统数据区的起始地址，则执行擦除，然后写入数据。
如果收到的地址不是用户系统数据区的起始地址，则不执行擦除，直接将数据写入对应地址。
当数据写完之后，将自动执行系统复位。
注意：AT32F435xx/AT32F437xx 在写完用户系统数据区之后不会自动执行系统复位，需要主机
端发送复位命令（Reset）。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待 4 字节的写入地址及
其 Checksum，当收到的地址和 Checksum 都有效时，响应主机 ACK，再等待 1 字节的写入长度及
其 Checksum，长度的值为实际要写入的值长度减 1(如写 10 字节数据，此值为 9)，当 Checksum 正
确时，将开始写入对应地址的数据，写完之后响应主机 ACK。
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @param addr
 * @param buf
 * @param len
 * @return int
 */
int at32_write_memory(at32_bup_t *b, uint32_t addr, uint8_t *buf, uint32_t len)
{
    // printf("\nStart Write Memory ...\n");
    if (len > 256)
    {
        printf("len too long, only 256 bytes!");
        return -1;
    }
    int ret = 0;
    uint8_t ack;
    uint8_t try = 0;
    if (!b->synced)
    {
        at32_sync(b);
    }
    do
    {
        ack = at32_send_cmd(b, AT32_CMD_Write_memory);
        if (ack != AT32_CMD_ACK)
        {
            printf("Write Memory cmd Failed! %d\n", ack);
            ret |= 0x1;
        }
        // b->delay_ms(1);
        uint8_t addrBuf[5];
        addrBuf[0] = (addr >> 24) & 0xFF;
        addrBuf[1] = (addr >> 16) & 0xFF;
        addrBuf[2] = (addr >> 8) & 0xFF;
        addrBuf[3] = addr & 0xFF;
        addrBuf[4] = 0;
        for(int i=0;i<4;i++)
        {
            addrBuf[4] ^= addrBuf[i];
        }
        // ack = at32_send_data(b, addrBuf, 5);
        b->write(addrBuf, 5);
        ack = at32_wait_ack(b);
        if (ack != AT32_CMD_ACK)
        {
            printf("send addr Failed! %d\n", ack);
            ret |= 0x2;
        }
        uint8_t writeLength = len - 1;
        uint8_t checksum = writeLength;
        for (uint16_t i = 0; i < len; i++)
        {
            checksum ^= buf[i];
        }
        // b->delay_ms(1);
        b->write(&writeLength, 1);
        for(int i=0;i<len;i++)
        {
            b->write(&buf[i], 1);
        }
        // b->delay_ms(1);
        // b->write(buf, len);
        // b->delay_ms(1);
        b->write(&checksum, 1);
        ack = at32_wait_ack(b);
        if (ack != AT32_CMD_ACK)
        {
            printf("send data Failed! at:0x%08X, len:%d %d\n", addr, len, ack);
            ret |= 0x4;
        }
    } while (ret != 0 && try++ < 1);
    return ret;
}
/**
 * @brief Erase 命令用于擦除主存储器，Erase 支持按 Sector 擦除（Sector 大小根据具体型号有区别），全擦
除等操作，对于存在 Bank2 的型号还支持 Bank1 擦除和 Bank2 擦除，支持 SPIM 的型号同时支持
Bank3 的擦除，擦除的地址必须是有效范围内的地址，各个型号的有效地址范围不同。
另外 AT32F435xx/AT32F437xx 还支持 Block 擦除。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待接收 2 字节数据，设
备根据这两个字节判断擦除类型：
 * 如果是 Sector 擦除，则表示擦除 Sector 的个数，接着再等待接收要擦除的 n 个 Sector 的索引，
当索引接收完成之后，会进行 Sector 擦除，擦除完成之后响应主机 ACK。
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @param count
 * @param sectors
 * @return int
 */
int at32_erase_sector(at32_bup_t *b, uint16_t count, uint16_t *sectors)
{
    printf("\nStart Erase ...\n");
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Erase);
    if (ack == AT32_CMD_ACK)
    {
        printf("Erase CMD OK!\n");
    }
    else
    {
        printf("Erase cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    // b->delay_ms(1);
    uint8_t *sendbuff = (uint8_t *)malloc(count * 2 + 2);
    sendbuff[0] = ((count - 1) >> 8) & 0xFF;
    sendbuff[1] = (count - 1) & 0xFF;
    for (int i = 0; i < count; i++)
    {
        sendbuff[2 + i * 2] = (sectors[i] >> 8) & 0xFF;
        sendbuff[3 + i * 2] = sectors[i] & 0xFF;
    }
    ack = at32_send_data(b, sendbuff, count * 2 + 2);
    if (ack == AT32_CMD_ACK)
    {
        printf("send sectors OK!\n");
    }
    else
    {
        printf("send sectors Failed! %d\n", ack);
        ret |= 0x2;
    }
    free(sendbuff);
    return ret;
}
/**
 * @brief 如果是 Block 擦除，则等待接收 4 个字节的 Block 起始地址及其 Checksum，如果 Checksum 正
确，则执行 Block 擦除，擦除完成之后响应主机 ACK。AT32F435xx 和 AT32F437xx 支持 Block
擦除，每个 Block 大小为 64KB。
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @param blockAddr
 * @return int
 */
int at32_earase_block(at32_bup_t *b, uint32_t blockAddr)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Erase);
    if (ack == AT32_CMD_ACK)
    {
        printf("Erase CMD OK!\n");
    }
    else
    {
        printf("Erase cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    // b->delay_ms(1);
    uint8_t cmdBuf[2] = {0xff, 0xfb};
    ack = at32_send_data(b, cmdBuf, 2);
    if (ack == AT32_CMD_ACK)
    {
        printf("send block erase cmd OK!\n");
    }
    else
    {
        printf("send block erase cmd Failed! %d\n", ack);
        ret |= 0x2;
    }
    uint8_t addrBuf[4];
    addrBuf[0] = (blockAddr >> 24) & 0xFF;
    addrBuf[1] = (blockAddr >> 16) & 0xFF;
    addrBuf[2] = (blockAddr >> 8) & 0xFF;
    addrBuf[3] = blockAddr & 0xFF;
    ack = at32_send_data(b, addrBuf, 4);
    if (ack == AT32_CMD_ACK)
    {
        printf("send block addr OK!\n");
        // b->delay_ms(1000);
        // b->synced = 0;
    }
    else
    {
        printf("send block addr Failed! %d\n", ack);
        ret |= 0x4;
    }
    return ret;
}
/**
 * @brief 如果全擦除或者 Bank 擦除，则直接执行对应的擦除动作，擦除完成之后响应主机 ACK
 *  此命令在访问保护开启时不能使用。
 * @param b
 * @param type 0:bank1, 1:bank2, 2:bank3, 3:all earse
 * @return int
 */
int at32_erase_bank(at32_bup_t *b, uint8_t type)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Erase);
    if (ack == AT32_CMD_ACK)
    {
        printf("Erase CMD OK!\n");
    }
    else
    {
        printf("Erase cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    // b->delay_ms(1);
    uint8_t cmdBuf[2] = {0xff, 0xfc};
    if (type > 3)
        type = 3;
    cmdBuf[1] = 0xfc + type;
    uint8_t checksum = at32_checksum(cmdBuf, 2);
    b->write(cmdBuf, 2);
    b->write(&checksum, 1);
    uint16_t eraseWait = 0;
    while (1)
    {
        if (b->read(&ack, 1) == 1)
        {
            break;
        }
        else
        {
            eraseWait++;
            if (eraseWait > 500)
            {
                break;
            }
            // b->delay_ms(10);
        }
    }
    // ack = at32_send_data(b, cmdBuf, 2);
    if (ack == AT32_CMD_ACK)
    {
        printf("send bank%d erase cmd OK!\n", 3 - type);
        // b->delay_ms(50);
        // b->synced = 0;
    }
    else
    {
        printf("send bank%d erase cmd Failed! %d\n", 3 - type, ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Erase and Program protect 命令用于保护指定的 sector 不能进行擦除和编程。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待 1 字节的长度-1（n）,
接着再接收 n+1 个字节对应擦写保护的 index bit 位（可参考对应系列型号的用户系统数据区擦写保
护 bit 位的定义），以及 1 字节的 Checksum，当 Checksum 有效时，设备将擦除用户系统数据区
（会保留用户系统数据区除擦写保护字节的其它数据），写入擦写保护的设定, 然后响应主机 ACK，
接着执行系统复位。
注意：擦写保护中的 index bit 取值为（0,1,2…n）,对应用户系统数据区中擦写保护字节（0-N）的 bit
位，更多详细内容可参考具体型号用户手册中的用户系统数据说明。
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @param secotorCount [0-256] number of sectors to be protect
 * @param sectors sectors to be protect
 * @return int
 */
int at32_erase_and_program_protect(at32_bup_t *b, uint16_t secotorCount, uint8_t *sectors)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Erase_and_program_protect);
    if (ack == AT32_CMD_ACK)
    {
        printf("Erase CMD OK!\n");
    }
    else
    {
        printf("Erase cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->delay_ms(1);
    uint8_t *sendbuff = (uint8_t *)malloc(secotorCount + 1);
    sendbuff[0] = secotorCount - 1;
    memcpy(sendbuff + 1, sectors, secotorCount);
    ack = at32_send_data(b, sendbuff, secotorCount + 1);
    if (ack == AT32_CMD_ACK)
    {
        printf("send sectors OK!\n");
    }
    else
    {
        printf("send sectors Failed! %d\n", ack);
        ret |= 0x2;
    }
    free(sendbuff);
    return ret;
}
/**
 * @brief Erase and program unprotect 命令用于解除存储器的擦写保护。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，然后执行解除所有 sector 的擦写
保护，再次响应主机 ACK，之后会执行系统复位。
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @return int
 */
int at32_erase_and_program_unprotect(at32_bup_t *b)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Erase_and_program_unprotect);
    if (ack == AT32_CMD_ACK)
    {
        printf("Erase CMD OK! (First ack, need 2)\n");
    }
    else
    {
        printf("Erase and program unprotect cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->delay_ms(100);
    if (b->read(&ack, 1) == 1)
    {
        if (ack == AT32_CMD_ACK)
        {
            printf("Erase CMD OK! (Second ack, done!)\n");
        }
        else
        {
            printf("Erase and program unprotect cmd Failed! %d\n", ack);
            ret |= 0x2;
        }
    }
    else
    {
        printf("get Second ack Failed!\n");
        printf("Erase and program unprotect cmd Failed! %d\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Access Protect 命令使能存储器的访问保护，使用此命令后，将不能读取到存储器的数据。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，然后执行使能访问保护功能，完
成之后响应主机 ACK，并启动系统复位。
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @return int
 */
int at32_access_protect(at32_bup_t *b)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Access_Protect);
    if (ack == AT32_CMD_ACK)
    {
        printf("Access protect CMD OK!\n");
    }
    else
    {
        printf("Access protect cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->delay_ms(100);
    if (b->read(&ack, 1) == 1)
    {
        if (ack == AT32_CMD_ACK)
        {
            printf("Access protect CMD OK! (Second ack, done!)\n");
        }
        else
        {
            printf("Access protect cmd Failed! %d\n", ack);
            ret |= 0x2;
        }
    }
    else
    {
        printf("get Second ack Failed!\n");
        printf("Access protect cmd Failed! %d\n", ack);
        ret |= 0x4;
    }
    return ret;
}
/**
 * @brief Access unprotect 命令用于解除存储器的访问保护，如果设备在访问保护模式下，调用此命令解除访
问保护，会自动擦除存储器所有数据。
当设备收到此命令后，响应主机 ACK，执行解除访问保护，完成之后响应主机 ACK，并执行系统复
位。
 *
 * @param b
 * @return int
 */
int at32_access_unprotect(at32_bup_t *b)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Access_Unprotect);
    if (ack == AT32_CMD_ACK)
    {
        printf("Access unprotect CMD OK!\n");
    }
    else
    {
        printf("Access unprotect cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->delay_ms(100);
    if (b->read(&ack, 1) == 1)
    {
        if (ack == AT32_CMD_ACK)
        {
            b->synced = 0; // device will reset, so sync again
            printf("Access unprotect CMD OK! (Second ack, done!)\n");
        }
        else
        {
            printf("Access unprotect cmd Failed! %d\n", ack);
            ret |= 0x2;
        }
    }
    else
    {
        printf("get Second ack Failed!\n");
        printf("Access unprotect cmd Failed! %d\n", ack);
        ret |= 0x4;
    }
    return ret;
}
/**
 * @brief Firmware CRC 命令用于校验存储器数据是否正确。主机可以指定存储器区域计算，但必须以 sector
为单位并且起始地址要对齐 sector。
当设备收到此命令后，则响应主机 ACK，接着会等待 4 字节的起始地址及其 1 字节的 Checksum，
当收到的地址和 checksum 都有效时，响应主机 ACK 后，接着再等待接收 2 字节需要计算 sector 的
个数-1（n）及其 1 字节的 Checksum，当 Checksum 正确时，响应主机 ACK，并开始计算 CRC，
计算完成之后，将 4 字节的 CRC 回传给主机。
Firmware CRC 使用 MPEG-2 CRC 算法。
 *
 * @param b
 * @param startAddress
 * @param sectorCount
 * @param crc
 * @return int
 */
int at32_firmware_crc(at32_bup_t *b, uint32_t startAddress, uint16_t sectorCount, uint32_t *crc)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Firmware_CRC);
    if (ack == AT32_CMD_ACK)
    {
        printf("Firmware CRC CMD OK!\n");
    }
    else
    {
        printf("Firmware CRC cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    uint8_t addrbuff[4];
    addrbuff[0] = (startAddress >> 24) & 0xFF;
    addrbuff[1] = (startAddress >> 16) & 0xFF;
    addrbuff[2] = (startAddress >> 8) & 0xFF;
    addrbuff[3] = startAddress & 0xFF;
    ack = at32_send_data(b, addrbuff, 4);
    if (ack == AT32_CMD_ACK)
    {
        printf("addresee OK!\n");
    }
    else
    {
        printf("addresee Failed! %d\n", ack);
        ret |= 0x2;
    }
    uint8_t countbuff[2];
    countbuff[0] = (sectorCount >> 8) & 0xFF;
    countbuff[1] = sectorCount & 0xFF;
    ack = at32_send_data(b, countbuff, 2);
    if (ack == AT32_CMD_ACK)
    {
        printf("send secotor count OK!\n");
    }
    else
    {
        printf("send secotor count Failed! %d\n", ack);
        ret |= 0x4;
    }
    b->delay_ms(100);
    if (b->read((uint8_t *)crc, 4) == 4)
    {
        printf("CRC: %08X\n", *crc);
    }
    else
    {
        printf("get CRC Failed!\n");
        ret |= 0x8;
    }
    return ret;
}
/**
 * @brief Enable SPIM
 *
 * @param b
 * @param type 0x90: FLASH, Dummycycle=4; 0x91:FLASH, Quad Enable, dummycycle=4
 * @param size flash size, max 16M bytes.
 * @param fda FLASH_FDA, how many bytes need to be encypted start from bank3
 * @return int
 */
int at32_enable_SPIM(at32_bup_t *b, uint8_t type, uint32_t size, uint32_t fda)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Enable_SPIM);
    if (ack == AT32_CMD_ACK)
    {
        printf("Enable SPIM CMD OK!\n");
    }
    else
    {
        printf("Enable SPIM cmd Failed! %d\n", ack);
    }
    uint8_t sendbuf[9];
    sendbuf[0] = type;
    sendbuf[1] = (size >> 24) & 0xFF;
    sendbuf[2] = (size >> 16) & 0xFF;
    sendbuf[3] = (size >> 8) & 0xFF;
    sendbuf[4] = size & 0xFF;
    sendbuf[5] = (fda >> 24) & 0xFF;
    sendbuf[6] = (fda >> 16) & 0xFF;
    sendbuf[7] = (fda >> 8) & 0xFF;
    sendbuf[8] = fda & 0xFF;
    ack = at32_send_data(b, sendbuf, 9);
    if (ack == AT32_CMD_ACK)
    {
        printf("send data OK!\n");
    }
    else
    {
        printf("send data Failed! %d\n", ack);
        ret |= 0x1;
    }
    return ret;
}
/**
 * @brief Enable sLib 命令用于使能 sLib 功能，更多 sLib 的使用说明请参考 sLib 的使用指南。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待 4 字节 sLib 密码，2
字节的 sLib 起始 sector，2 字节的 sLib 数据/指令起始 sector， 2 字节的 sLib 结束 sector，和 1 字
节的 Checksum，当 Checksum 有效时，开始设定 sLib 配置，设置完成之后，返回 1 字节的设定状
态，并响应主机 ACK。另外 sLib 的设定需要系统复位之后才能生效。
此命令在访问保护开启时不能使用。
sLib 状态：
 1：表示当前 sLib 已经使能
 0：表示 sLib 配置成功
 *
 * @param b
 * @param password
 * @param start
 * @param data
 * @param end
 * @return int
 */
int at32_enable_sLib(at32_bup_t *b, uint32_t password, uint16_t start, uint16_t data, uint16_t end)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Enable_sLib);
    if (ack == AT32_CMD_ACK)
    {
        printf("Enable sLib CMD OK!\n");
    }
    else
    {
        printf("Enable sLib cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    uint8_t sendbuff[10];
    sendbuff[0] = (password >> 24) & 0xFF;
    sendbuff[1] = (password >> 16) & 0xFF;
    sendbuff[2] = (password >> 8) & 0xFF;
    sendbuff[3] = password & 0xFF;
    sendbuff[4] = (start >> 8) & 0xFF;
    sendbuff[5] = start & 0xFF;
    sendbuff[6] = (end >> 8) & 0xFF;
    sendbuff[7] = end & 0xFF;
    sendbuff[8] = (data >> 8) & 0xFF;
    sendbuff[9] = data & 0xFF;
    ack = at32_send_data(b, sendbuff, 10);
    if (ack == AT32_CMD_ACK)
    {
        printf("send data OK!\n");
    }
    else
    {
        printf("send data Failed! %d\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Disable sLib 命令用于解除 sLib 功能，解除 sLib 会擦除存储器所有数据。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待 4 字节的 Passwrod
和 1 字节的 Checksum，当 Checksum 有效时，执行解除 sLib 操作，并返回 1 字节的解除状态，并
响应主机 ACK。
sLib 解除状态：
 1：表示 sLib 密码错误
 0：表示 sLib 解除成功
此命令在访问保护开启时不能使用。
 *
 * @param b
 * @param password
 * @return int
 */
int at32_disable_sLib(at32_bup_t *b, uint32_t password)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Disable_sLib);
    if (ack == AT32_CMD_ACK)
    {
        printf("Disable sLib CMD OK!\n");
    }
    else
    {
        printf("Disable sLib cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    uint8_t sendbuff[4];
    sendbuff[0] = (password >> 24) & 0xFF;
    sendbuff[1] = (password >> 16) & 0xFF;
    sendbuff[2] = (password >> 8) & 0xFF;
    sendbuff[3] = password & 0xFF;
    ack = at32_send_data(b, sendbuff, 4);
    if (ack == AT32_CMD_ACK)
    {
        printf("send password OK!\n");
    }
    else
    {
        printf("send password Failed! %d\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Get sLib status 命令用于获取当前 sLib 状态，此命名返回对应 sLib 寄存器值，寄存器值含义参考具
体系列用户手册中关于 sLib 寄存器的说明。
AT32F435xx/AT32F437xx:（返回 16 bytes 数据）
当设备收到此命令后，则响应主机 ACK，接着会返回 4 字节的 SLIB_STS0 寄存器的值，4 字节的
SLIB_STS1 寄存器值，4 字节的 SLIB_STS2 寄存器值，4 字节 SLIB_MISC_STS 寄存器值，最后再
响应主机 ACK。
其它系列：（返回 12 bytes 数据）
当设备收到此命令后，则响应主机 ACK，接着会返回 4 字节的 SLIB_STS0 寄存器的值，4 字节的
SLIB_STS1 寄存器值， 4 字节 SLIB_MISC_STS 寄存器值，最后再响应主机 ACK。
注意：此命令在 AT32F403Axx, AT32F407xx, AT32F413xx 开启访问保护后不能使用，其它系列可以
使用
 *
 * @param b
 * @param status
 * @return int
 */
int at32_get_sLib_status(at32_bup_t *b, uint8_t *status)
{
#define IS_AT32F435_OR_AD32F437 1 // FIXME Find the mcu model!
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Get_sLib_Status);
    if (ack == AT32_CMD_ACK)
    {
        // printf("Get sLib Status CMD OK!\n");
    }
    else
    {
        printf("Get sLib Status cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->delay_ms(10);
    uint8_t readbyte = IS_AT32F435_OR_AD32F437 ? 16 : 12;
    int rb = b->read(status, 16);
    // if (rb == 1)
    // {
    //     printf("recv data OK!\n");
    // }
    // else
    // {
    //     printf("recv data Failed! %d\n", ack);
    //     ret |= 0x2;
    // }

    b->read(&ack, 1);
    if (ack == AT32_CMD_ACK)
    {
        // printf("recv ack OK!\n");
    }
    else
    {
        // printf("recv ack Failed! %d\n", ack);
        ret |= 0x4;
    }
    return ret;
}
/**
 * @brief
 *
 * @param b
 * @param remap Remap Flag：
 * 0：SCK/PB1 CS/PA8 IO0/PA11 IO1/PA12 IO2/PB7 IO3/PB6
 * 1：SCK/PB1 CS/PA8 IO0/PB10 IO1/PB11 IO2/PB7 IO3/PB6
 * @return int
 */
int at32_SPIM_Remap(at32_bup_t *b, uint8_t remap)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_SPIM_Remap);
    if (ack == AT32_CMD_ACK)
    {
        printf("SPIM Remap CMD OK!\n");
    }
    else
    {
        printf("SPIM Remap cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    uint8_t sendbuff[1];
    sendbuff[0] = remap;
    ack = at32_send_data(b, sendbuff, 1);
    if (ack == AT32_CMD_ACK)
    {
        printf("send data OK!\n");
    }
    else
    {
        printf("send data Failed! %d\n", ack);
        ret |= 0x2;
    }
    return ret;
}
/**
 * @brief Reset Device 命令用于设备执行系统复位。
当设备收到此命令后，响应主机 2 次 ACK，然后执行系统复位
 *
 * @param b
 * @return int
 */
int at32_reset_device(at32_bup_t *b)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Reset_Device);

    if (ack == AT32_CMD_ACK)
    {
        printf("Reset Device CMD OK!\n");
    }
    else
    {

        printf("Reset Device cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->delay_ms(10);
    b->read(&ack, 1);
    if (ack == AT32_CMD_ACK)
    {
        printf("recv 2nd ack OK!\n");
    }
    else
    {
        printf("recv 2nd ack Failed! %d\n", ack);
        ret |= 0x4;
    }
    return ret;
}
/**
 * @brief Advanced Access Protect 命令用于开启高级访问保护，高级读保护功能具体功能请参考对应型号用
户手册中对高级读保护的说明。
当设备收到此命令后，如果访问保护没有开启，则响应主机 ACK，接着会等待 2 字节的高级访问保
护 Flag（Flag 可以是任意值），开始设定高级访问保护的配置，响应主机 ACK 之后，执行系统复
位。
此命令在访问保护开启时不能使用。
注意：高级访问保护在部分型号上不能再被解除，具体参考对于型号用户手册。
 *
 * @param b
 * @param flag
 * @return int
 */
int at32_advanced_Access_protect(at32_bup_t *b, uint8_t *flag)
{
    int ret = 0;
    uint8_t ack;
    if (!b->synced)
    {
        at32_sync(b);
    }
    ack = at32_send_cmd(b, AT32_CMD_Advanced_Access_Protect);
    if (ack == AT32_CMD_ACK)
    {
        printf("Advanced Access Protect CMD OK!\n");
    }
    else
    {
        printf("Advanced Access Protect cmd Failed! %d\n", ack);
        ret |= 0x1;
    }
    b->write(flag, 2);
    b->delay_ms(10);
    b->read(&ack, 1);
    if (ack == AT32_CMD_ACK)
    {
        printf("recv ack OK!\n");
    }
    else
    {
        printf("recv ack Failed! %d\n", ack);
        ret |= 0x4;
    }
    return ret;
}

/**
 * @brief  ./at32_isploader_v3_win.exe --comport COM3 115200 --isp --getversion --getdeviceid --erase bank1 --writefile ./ZINO_AT32F421C8T7_BOOTLOOADER_V1.0.1_20250124.bin 0x08000000
 *
 */
